﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Web;
using System.Web.Compilation;
using System.IO;
using System.Collections.Specialized;
using System.Linq.Expressions;
using System.Data;

namespace System.Web.Mvc.BindingHelpers {
    public static class BindingHelperExtensions {
        
        #region Form-Read Helpers
        public static bool ContainsKey(this NameValueCollection coll, string keyToFind) {
            bool bResult = false;
            foreach (string key in coll) {
                if (key.ToLower().Trim().Equals(keyToFind.ToLower().Trim())) {
                    bResult = true;
                    break;
                }
            }
            return bResult;
        }

        /// <summary>
        /// Reads in values from a NameValueCollection (like Request.Form, Cookies, Session, or QueryString) and sets the properties
        /// on this object. The names of the values must be Type.PropertyName - i.e. "Product.ProductName"
        /// </summary>
        /// <param name="values">The NameValueCollection to read from (i.e. Request.Form)</param>
        /// <param name="keys">The keys to use</param>
        public static void UpdateFrom(this object obj, NameValueCollection values, params string[] keys) {

            if (keys.Length > 0) {

                NameValueCollection newList = new NameValueCollection();
                foreach (string key in keys) {
                    if (values.ContainsKey(key)) {
                        newList.Add(key, values[key]);
                    }
                }
                UpdateFrom(obj, newList);
            }

        }
        /// <summary>
        /// Reads in values from a NameValueCollection (like Request.Form, Cookies, Session, or QueryString) and sets the properties
        /// on this object. The names of the values must be Type.PropertyName - i.e. "Product.ProductName"
        /// </summary>
        /// <param name="values">Request.Form, QueryString, Parameter, etc</param>
        public static void UpdateFrom(this object obj, NameValueCollection values) {
            UpdateFrom(obj, values, "");
        }


        /// <summary>
        /// Reads in values from a NameValueCollection (like Request.Form, Cookies, Session, or QueryString) and sets the properties
        /// on this object. The names of the values must be Type.PropertyName - i.e. "Product.ProductName"
        /// </summary>
        /// <param name="values">Request.Form, QueryString, Parameter, etc</param>
        /// <param name="objectPrefix">A prefix for the Keys in the Namevalue collection - "Product." in "Product.ProductName" for example</param>
        public static void UpdateFrom(this object obj, NameValueCollection values, string objectPrefix) {
            Type objType = obj.GetType();
            string objName = objType.Name;
            StringBuilder exceptionList = new StringBuilder();

            PropertyInfo[] fields = objType.GetProperties();

            PopulateTypeException ex =null;

            foreach (PropertyInfo property in fields) {

                //check the key
                //going to be forgiving here, allowing for full declaration
                //or just propname
                string httpKey = property.Name;

                if (objectPrefix != string.Empty)
                    httpKey = objectPrefix + httpKey;

                if (values[httpKey] == null) {
                    httpKey = objName + "." + property.Name;
                }

                if (values[httpKey] == null) {
                    httpKey = objName + "_" + property.Name;
                }


                if (values[httpKey] != null) {
                    TypeConverter conv = TypeDescriptor.GetConverter(property.PropertyType);
                    object value = values[httpKey];
                    
                    if (conv.CanConvertFrom(typeof(string))) {
                        try {
                            value = conv.ConvertFrom(values[httpKey]);
                            property.SetValue(obj, value, null);
                        
                        } catch (Exception e) {
                            string message = property.Name + " is not a valid " + property.PropertyType.Name+"; "+e.Message;
                            if (ex == null)
                                ex = new PopulateTypeException("Errors occurred during object binding - review the LoadExceptions property of this exception for more details");

                            PopulateTypeException.ExceptionInfo info = new PopulateTypeException.ExceptionInfo();
                            info.AttemptedValue = value;
                            info.PropertyName = property.Name;
                            info.ErrorMessage = message;

                            ex.LoadExceptions.Add(info);

                        }
                    } else {
                        throw new Exception("No type converter available for type: " + property.PropertyType);
                    }
                }
            }
            if (ex != null)
                throw ex;

        }
        

        [global::System.Serializable]
        public class PopulateTypeException : Exception {

            public class ExceptionInfo {
                public string PropertyName { get; set; }
                public object AttemptedValue { get; set; }
                public string ErrorMessage { get; set; }
            }

            public List<ExceptionInfo> LoadExceptions = new List<ExceptionInfo>();
            private PopulateTypeException() { }
            public PopulateTypeException(string message)
                : base(message) {
            }
            public PopulateTypeException(string message, Exception inner) : base(message, inner) { }
            protected PopulateTypeException(
              System.Runtime.Serialization.SerializationInfo info,
              System.Runtime.Serialization.StreamingContext context)
                : base(info, context) { }
        }

        #endregion

        /// <summary>
        /// Gets a POST/GET/QueryString value passed in
        /// </summary>
        /// <param name="key">The form/querystring parameter name</param>
        /// <returns>System.String</returns>
        public static string ReadFromRequest(this Controller controller, string key) {
            IHttpContext context = controller.ControllerContext.HttpContext;
            string result = string.Empty;
            
            if (context != null) {
                
                //check POST
                if (context.Request.Form[key] != null)
                    result = context.Request.Form[key].ToString();

                if (result == string.Empty) {
                    //then QueryString
                    if (context.Request.QueryString[key] != null)
                        result = context.Request.QueryString[key].ToString();

                }

            }
            return result;

        }

    }
}
